package org.aspire.util;

import java.util.Hashtable;
import java.util.Vector;

import org.aspire.core.sys.GSystem;
import org.aspire.data.Data;

/******************************************************************************
 * A cache is a storage for a collection of objects or primitive data.
 * <p>
 * These objects and/or primitive data are time-stamped to ensure that each
 * item stored in the cache is unique even if the same data already exists.
 * This property can be disabled so that the cache will only accept unique
 * data.
 * 
 * @author JPACarabuena
 *****************************************************************************/
public class Cache {

	// ========================================================================
	// Constants
	// ========================================================================

	// ========================================================================
	// Static Methods
	// ========================================================================

	// ========================================================================
	// Fields
	// ========================================================================

	/**************************************************************************
	 * Whether or not time-stamping is enabled. Time-stamp serves as the
	 * unique key identifier for each object or primitive data.
	 * 
	 * Default: true
	 *************************************************************************/
	private boolean timeStampEnabled = true;
	
	/**************************************************************************
	 * A time-stamped cache for storing the object with the time-stamp, in long
	 * integer precision, as the key identifier.
	 *************************************************************************/
	private Hashtable<Long, Object> timeStampCache;
	
	/**************************************************************************
	 * A unique cache for storing the object.
	 *************************************************************************/
	private Vector<Object> uniqueCache;
	
	/**************************************************************************
	 * Contains the time-stamp of objects sorted in ascending order.
	 *************************************************************************/
	private Long[] sortedTimeStamp;
	
	// ========================================================================
	// Constructors
	// ========================================================================

	/**************************************************************************
	 * Create a new cache.
	 *************************************************************************/
	public Cache() {
		this.timeStampCache = new Hashtable<Long, Object>(100, 0.75f);
		this.uniqueCache = new Vector<Object>(100);
	}
	
	// ========================================================================
	// Getters & Setters
	// ========================================================================

	/**************************************************************************
	 * Checks if time-stamping in the cache is enabled.
	 * 
	 * @return whether or not time-stamping is enabled.
	 *************************************************************************/
	public boolean isTimeStampingEnabled() {
		return this.timeStampEnabled;
	}
	
	/**************************************************************************
	 * Enables or disable time-stamping in the cache.
	 * 
	 * @param timeStamping whether or not time-stamping is enabled.
	 *************************************************************************/
	public void setTimeStamping(boolean timeStamping) {
		if(this.size() == 0) {
			this.timeStampEnabled = timeStamping;
		}
	}
	
	// ========================================================================
	// Override Methods
	// ========================================================================

	// ========================================================================
	// Methods
	// ========================================================================

	/**************************************************************************
	 * Insert a value to the cache. If time-stamping is enabled, the current
	 * time-stamp is generated and used as the unique key identifier for the
	 * value.
	 * 
	 * @param value the object to be inserted.
	 *************************************************************************/
	public void insert(Object value) {
		if(this.timeStampEnabled) {
			this.timeStampCache.put(GSystem.getTime(), value);
			this.generateSortedTimeStamp();
			// let thread sleep so that the time-stamp will not be
			// the same for consecutive insertion.
			try {
				Thread.sleep(2);
			} catch(InterruptedException e) {
				
			}
		} else {
			if(this.uniqueCache.contains(value)) {
				this.uniqueCache.add(value);
			}
		}
	}
	
	/**************************************************************************
	 * Retrieve the element at the specified index. If the index is out of
	 * bounds, a null value will be returned instead.
	 * 
	 * @param i	the index at which the object to be retrieved is stored.
	 * @return the element to be retrieved.
	 *************************************************************************/
	public Object elementAt(int i) {
		if(this.timeStampEnabled) {
			if(this.timeStampCache.size() <= i || i < 0) {
				return null;
			}
			return this.timeStampCache.get(this.sortedTimeStamp[i]);
		} else {
			if(this.uniqueCache.size() <= i || i < 0) {
				return null;
			}
			return this.uniqueCache.elementAt(i);
		}
	}
	
	/**************************************************************************
	 * Remove an element at the specified index. The object removed is
	 * returned. If the index is out of bounds, a null value is returned
	 * instead.
	 * 
	 * @param i the index of the element to be removed from the cache.
	 * @return the object removed from the cache.
	 *************************************************************************/
	public Object remove(int i) {
		if(this.timeStampEnabled) {
			if(this.timeStampCache.size() <= i || i < 0) {
				return null;
			}
			Object retVal = this.timeStampCache.remove(this.sortedTimeStamp[i]);
			this.generateSortedTimeStamp();
			return retVal;
		} else {
			if(this.uniqueCache.size() <= i || i < 0) {
				return null;
			}
			return this.uniqueCache.remove(i);
		}
	}
	
	/**************************************************************************
	 * Remove all the contents of the cache.
	 *************************************************************************/
	public void removeAll() {
		if(this.timeStampEnabled) {
			this.timeStampCache.clear();
		} else {
			this.uniqueCache.clear();
		}
	}
	
	/**************************************************************************
	 * Returns the current size of the cache.
	 * 
	 * @return	the size of the cache.
	 *************************************************************************/
	public int size() {
		if(this.timeStampEnabled) {
			return this.timeStampCache.size();
		} else {
			return this.uniqueCache.size();
		}
	}
	
	/**************************************************************************
	 * Checks if the cache has no contents.
	 * 
	 * @return whether or not the cache is empty.
	 *************************************************************************/
	public boolean isEmpty() {
		return this.size() == 0;
	}
	
	/**************************************************************************
	 * Returns the index of the specified object. If the object does not exist
	 * in the cache, a value of -1 is returned instead.
	 * 
	 * @param value	the object which the index this function will try to get.
	 * @return the index of the specified object.
	 *************************************************************************/
	public int indexOf(Object value) {
		if(this.timeStampEnabled) {
			if(this.timeStampCache.contains(value)) {
				for(int i=0; i<this.size(); i++) {
					if(this.timeStampCache.get(sortedTimeStamp[i]) == value) {
						return i;
					}
				}
			}
			return -1;
		} else {
			if(this.uniqueCache.contains(value)) {
				return this.uniqueCache.indexOf(value);
			}
			return -1;
		}
	}
	
	/**************************************************************************
	 * Updates the array containing the time-stamp sorted in ascending order.
	 *************************************************************************/
	private void generateSortedTimeStamp() {
		Object[] array = Array.getArray(this.timeStampCache.keys(), this.size());
		Object[] sorted = Sort.shellSort(array, Data.LONG, Sort.ASCENDING);
		this.sortedTimeStamp = new Long[this.size()];
		for(int i=0; i<this.size(); i++) {
			this.sortedTimeStamp[i] = (Long) sorted[i];
		}
	}
	
	// ========================================================================
	// Inner & Anonymous Classes
	// ========================================================================
}